﻿using System;
using System.Collections.Generic;
using System.Reflection;

namespace ArgusLib.Reflection
{
	public static class TypeInitializer
	{
		public static object CreateInstance(Type type, bool nonPublic)
		{
			try
			{
				return Activator.CreateInstance(type, nonPublic);
			}
			catch (MissingMethodException exception)
			{
			}

			BindingFlags flags = BindingFlags.Public;
			if (nonPublic == true)
				flags |= BindingFlags.NonPublic;

			//ConstructorInfo[] cis = type.GetConstructors(flags | BindingFlags.Instance);
			//foreach (ConstructorInfo ci in cis)
			//{
			//	object[] attributes = ci.GetCustomAttributes(typeof(ConstructorArgumentsAttribute), false);
			//	if (attributes.Length < 1)
			//		continue;

			//	ConstructorArgumentsAttribute args = (ConstructorArgumentsAttribute)attributes[0];
			//	return ci.Invoke(args.Arguments);
			//}
			//cis = null;

			MethodInfo[] mis = type.GetMethods(flags | BindingFlags.Static | BindingFlags.FlattenHierarchy);
			foreach (MethodInfo mi in mis)
			{
				if (type != mi.ReturnType && mi.ReturnType.IsSubclassOf(type) == false)
					continue;

				ParameterInfo[] pis = mi.GetParameters();
				if (pis.Length == 0)
					return mi.Invoke(null, null);

				//object[] attributes = mi.GetCustomAttributes(typeof(StandardArgumentsAttribute), false);
				//if (attributes.Length < 1)
				//	continue;

				//StandardArgumentsAttribute args = (StandardArgumentsAttribute)attributes[0];
				//return mi.Invoke(null, args.Arguments);
			}
			mis = null;

			ParameterInfo[] parameterInfos;
			ConstructorInfo ci = TypeInitializer.GetBestConstructor(type, nonPublic, out parameterInfos);
			if (ci == null)
				throw new MissingMethodException();
			object[] args = new object[parameterInfos.Length];
			for (int i = 0; i < args.Length; i++)
			{
				args[i] = TypeInitializer.CreateInstance(parameterInfos[i].ParameterType, nonPublic);
			}

			return ci.Invoke(args);
		}

		private static ConstructorInfo GetBestConstructor(Type type, bool nonPublic, out ParameterInfo[] parameterInfos)
		{
			BindingFlags flags = BindingFlags.Instance| BindingFlags.Public;
			if (nonPublic == true)
				flags |= BindingFlags.NonPublic;
			ConstructorInfo[] cis = type.GetConstructors(flags);
			int index = -1;
			int structParams = int.MaxValue;
			int Params = int.MaxValue;
			parameterInfos = null;

			for (int i = 0; i < cis.Length; i++)
			{
				ParameterInfo[] pis = cis[i].GetParameters();
				if (pis.Length < 1)
					return cis[i];

				int sP = 0;
				int p = 0;
				bool Continue = false;
				foreach (ParameterInfo pi in pis)
				{
					if (pi.ParameterType == type)
					{
						Continue = true;
						break;
					}
					if (pi.ParameterType.IsValueType == true)
						sP++;
					else
						p++;
				}

				if (Continue == true)
					continue;

				if (p < Params || (p == Params && sP < structParams))
				{
					index = i;
					structParams = sP;
					Params = p;
					parameterInfos = pis;
				}
			}

			if (index < 0)
				return null;
			return cis[index];
		}
	}
}
